package ru.amse.baltijsky.javascheme;

import ru.amse.baltijsky.javascheme.exporter.image.IImageExporter;
import ru.amse.baltijsky.javascheme.exporter.image.IImageExporterFactory;
import ru.amse.baltijsky.javascheme.exporter.image.ImageExporterFactory;
import ru.amse.baltijsky.javascheme.exporter.stream.IStreamExporterFactory;
import ru.amse.baltijsky.javascheme.exporter.stream.StreamExporterFactory;
import ru.amse.baltijsky.javascheme.frames.main.MainFrame;
import ru.amse.baltijsky.javascheme.importer.IImporter;
import ru.amse.baltijsky.javascheme.importer.IImporterFactory;
import ru.amse.baltijsky.javascheme.importer.ImporterFactory;
import ru.amse.baltijsky.javascheme.tree.SchemaNode;
import ru.amse.baltijsky.javascheme.tree.SchemaTree;
import ru.amse.baltijsky.javascheme.tree.walker.DepthFirstTreeWalker;
import ru.amse.baltijsky.javascheme.util.Util;

import java.io.*;

/**
 * The main class of the application containing the entry point.
 * Imports the data using importer and outputs it via exporter corresponding to the aliases
 * provided in the program arguments.
 */
public class JavaScheme {
    /**
     * Special command to perform tests.
     */
    private static final String TEST_COMMAND = "-test";
    /**
     * Export to jpeg command.
     */
    private static final String TOIMAGE_COMMAND = "-i";
    /**
     * Command to export the jpg image to the specified location.
     */
    private static final String DEST_COMMAND = "-o";
    /**
     * Export to text file command.
     */
    private static final String TOTEXT_COMMAND = "-text";
    /**
     * Importer factory.
     */
    private static IImporterFactory importerFactory = new ImporterFactory();
    /**
     * Stream exporter factory.
     */
    private static IStreamExporterFactory streamExporterFactory = new StreamExporterFactory();
    /**
     * Image exporter factory.
     */
    private static IImageExporterFactory imageExporterFactory = new ImageExporterFactory();

    /**
     * Parses program parameters and performs different tasks basing on the user's request. If no scenario is implemented
     * for the given program parameters, prints out program usage.
     * For detailed information on what the program can do, see printUsage() method.
     *
     * @param args see printUsage()
     * @see ru.amse.baltijsky.javascheme.JavaScheme#printUsage();
     */
    public static void main(String[] args) {
        if ((args.length == 1 && (args[0].equalsIgnoreCase("help") || args[0].equalsIgnoreCase("/?"))) || args.length > 7) {
            printUsage();
        } else {
            // parse program parameters
            try {
                boolean toImage = args.length > 0 && args[0].equals(TOIMAGE_COMMAND);
                boolean toText = args.length > 0 && args[0].equals(TOTEXT_COMMAND);
                if (args.length == 0) {
                    MainFrame.runFrame(importerFactory);
                } else if (args.length == 2 && args[0].equals(TEST_COMMAND)) {
                    startTest(importerFactory.createImporter("test"), args[1]);
                } else if (args.length == 2) {
                    runMainFrame(args[0], "", args[1]);
                } else if (args.length == 3 && !toImage && !toText) {
                    runMainFrame(args[0], args[1], args[2]);
                } else if (args.length == 4 && toImage) {
                    processToImageFile(importerFactory.createImporter("java"), args[1], args[2], "", args[3], "");
                } else if (args.length == 5 && toImage) {
                    processToImageFile(importerFactory.createImporter("java"), args[1], args[2], args[3], args[4], "");
                } else if (args.length == 6 && toImage) {
                    processToImageFile(importerFactory.createImporter("java"), args[1], args[2], "", args[3], args[5]);
                } else if (args.length == 7 && toImage) {
                    processToImageFile(importerFactory.createImporter("java"), args[1], args[2], args[3], args[4], args[6]);
                } else if (args.length == 3 && toText) {
                    processToStream(importerFactory.createImporter("java"), args[1], "", args[2], "text", "");
                } else if (args.length == 4 && toText) {
                    processToStream(importerFactory.createImporter("java"), args[1], args[2], args[3], "text", "");
                } else if (args.length > 0) {
                    printUsage();
                    return;
                }
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }

    /**
     * Imports schema tree from the specified path (for the certain class method) and exports it to the stream using the
     * exporter with the specified alias to the given destination.
     *
     * @param importer      importer object
     * @param importPath    the path from which to import
     * @param className     name of the class for the method of which to create schema tree
     * @param methodName    name of the method to create schema tree for
     * @param exporterAlias name of the stream exporter to be used
     * @param exporterDest  where the exporter should write the stream. If an empty string is provided, exports
     *                      to standard console output.
     * @throws IOException if any of the input/output errors occurs
     */
    private static void processToStream(IImporter importer, String importPath, String className, String methodName, String exporterAlias, String exporterDest) throws IOException {
        PrintWriter out = null;
        Reader in = null;
        try {
            in = new BufferedReader(new FileReader(importPath));
            if (exporterDest.equals("")) {
                out = new PrintWriter(System.out);
            } else {
                out = new PrintWriter(new FileWriter(exporterDest));
            }
            SchemaTree tree = importer.loadTree(in, className, methodName);
            streamExporterFactory.createExporter(exporterAlias).export(out, new DepthFirstTreeWalker<SchemaNode>(tree.getRoot()));
        } catch (IOException e) {
            System.out.println("import error: " + e.getMessage());
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }

    /**
     * Runs the GUI application with the preopened schema for the specified class and method (using the source code
     * on the specified path).
     *
     * @param path       path to the source code file containing the class for which to build the schema
     * @param className  name of the class for the method of which to build the scheme. May be empty string, then the first
     *                   public class found in the source file is used
     * @param methodName name of the method for which to  build the scheme
     * @throws IOException if the specified source code file was not found (search starts from the current directory).
     */
    private static void runMainFrame(String path, String className, String methodName) throws IOException {
        try {
            MainFrame.runFrame(importerFactory, path, className, methodName);
        } catch (FileNotFoundException e) {
            throw new IOException("import error: " + e.getMessage());
        }
    }

    /**
     * Runs tests for the visual representation subsystem reading test properties files and creating a bunch of
     * test scheme trees. Trees are exported into .jpg files with a separate .description file containing textual
     * desription of the genereted test tree, all files in the same directory named after the test properties file name.
     *
     * @param testImporter       importer for the test trees
     * @param testPropertiesPath path to the test properties .xml file
     * @throws java.io.IOException if any IO error occurs while reading test properties file
     */
    private static void startTest(IImporter testImporter, String testPropertiesPath) throws IOException {
        Reader in = new BufferedReader(new FileReader(testPropertiesPath));
        SchemaTree[] trees;
        try {
            trees = testImporter.loadTrees(in);
        } finally {
            if (in != null) {
                in.close();
            }
        }

        String bunchName = new File(testPropertiesPath).getName();
        bunchName = bunchName.substring(0, bunchName.lastIndexOf("."));
        String destDirPath = "test" + File.separator + bunchName + " images";

        File destDir = new File(destDirPath);
        destDir.mkdirs();
        for (File f : destDir.listFiles()) {
            f.delete();
        }

        String fileNameFmt = "%s%0" + Integer.toString(Util.nDigits(trees.length)) + "d%s";
        for (int i = 0; i < trees.length; ++i) {
            System.out.print("writing test scheme " + i + " ... ");
            IImageExporter exporter = imageExporterFactory.createExporter(trees[i]);
            try {
                File f = new File(String.format(fileNameFmt, destDirPath + File.separator + "scheme ", i, ".jpg"));
                f.createNewFile();
                exporter.exportToFile(f, "jpg");
                System.out.print("image ok ... ");
            } catch (IOException e) {
                System.out.print("Couldn't export scheme to jpg: " + e.getMessage());
            }

            Writer writer = null;
            try {
                writer = new BufferedWriter(new FileWriter(String.format(fileNameFmt, destDirPath + File.separator + "scheme ", i, ".description")));
                writer.write(exporter.getDescription());
                System.out.println("description ok. done");
            } catch (IOException exception) {
                System.out.println("Couldn't write scheme description");
            } finally {
                try {
                    if (writer != null)
                        writer.close();
                } catch (IOException e) {
                    // nothing
                }
            }
        }
    }

    /**
     * Imports schema tree for the specified class method using source code in the srcPath and exports to image in the
     * specified image format.
     *
     * @param importer    importer object
     * @param formatName  informal name of the image format
     * @param srcPath     path to the source file containing the class for the methods of which to create the scheme
     * @param className   name of the class for which to create the scheme
     * @param methodName  name of the method for which to create the scheme
     * @param destination destination path where to write the
     * @throws IOException              if any error occurs while exporting the tree to the image or reading the source file
     * @throws IllegalArgumentException thrown if the image format name is unknown
     */
    private static void processToImageFile(IImporter importer, String formatName, String srcPath, String className, String methodName, String destination) throws IOException, IllegalArgumentException {
        System.out.print("started exporting image ... ");
        SchemaTree tree;
        Reader in = null;
        try {
            in = new BufferedReader(new FileReader(srcPath));
            tree = importer.loadTree(in, className, methodName);
        } catch (IOException e) {
            throw new IOException("import error: " + e.getMessage(), e);
        } finally {
            if (in != null) {
                in.close();
            }
        }
        try {
            String destPath = destination;
            if (destPath.equals("")) {
                destPath = className + (className.length() > 0 ? "." : "") + methodName + "." + formatName;
            }
            File f = new File(destPath);
            f.createNewFile();
            imageExporterFactory.createExporter(tree).exportToFile(f, formatName);
            System.out.println("done.");
        } catch (IOException e) {
            throw new IOException("couldn't export scheme to jpg: " + e.getMessage(), e);
        }
    }

    /**
     * Prints the usage of the application to the standard output stream.
     */
    private static void printUsage() {
        System.out.println("usage: (optional parameters in [square brackets])");
        System.out.println("   " + TOIMAGE_COMMAND + "<name of the image format> <path to .java file> [<class name>] <method name>" + "[" + DEST_COMMAND + "<path to .jpg file where to export>]");
        System.out.println("     creates java scheme for the specified method of the specified class and exports to image file of the specified format;");
        System.out.println("     e.g., you can specify \"jpg\" or \"bmp\"");
        System.out.println("     if no class name is specified, searches for the class with the same name as the java source file.");
        System.out.println("     if path to .jpg file is specified, writes to the path, otherwise to the file named like <class>.<method>.jpg in the current dir");
        System.out.println("   <path to .java file> [<class name>] <method name>");
        System.out.println("     creates java scheme for the specified method of the specified class and displays it on the screen.");
        System.out.println("     if no class name is specified, searches for the class with the same name as the java source file.");
        System.out.println("   [no parameters] : starts a GUI app");
        System.out.println("   " + TOTEXT_COMMAND + " <path to .java file> [<class name>] <method name>");
        System.out.println("     prints to standard output the textual representation of the schema tree for the specified class method.");
        System.out.println("     if no class name is specified, searches for the class with the same name as the java source file.");
        System.out.println("   " + TEST_COMMAND + " <path to .xml file with test properties>");
        System.out.println("     starts test suite with the task specified by the test properties file");
    }
}   